내용 참고
문제
첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.
n번째 피보나치 수를 출력한다.
F0=0, F1=1, Fn=Fn−1+Fn−2 (n≥2)
풀이
보통 피보나치 연산은 memoization 기법을 통해 O(N) 시간 만에 해결할 수 있지만, 이 문제의 경우 N의 크기가 매우 크므로, 제한시간안에 해결할 수 없다.
피보나치 연산을 행렬로 표현하여 O(log(N)) 시간 안에 해결할 수 있다.
(1) 피보나치 수열 정의
Fn+2=Fn+1+Fn
∴Fn+2=(11)(Fn+1Fn)
(2) 일반적 성질
Fn+1=1⋅Fn+1+0⋅Fn
∴Fn+1=(10)(Fn+1Fn)
(1), (2) 를 합치면 다음과 같이 쓸 수 있다.
∴(Fn+2Fn+1)=(1110)(Fn+1Fn)
let Mn=(Fn+1Fn)
∴Mn+1=(1110)Mn (n≥0)
M0=(F1F0)=(10)
M1=(1110)M0=(1110)(10)=(11)
...
∴Mn=(1110)nM0=(1110)n(10)
이 때 생성되는 제곱 연산을 분할정복을 통해 계산하면, 피보나치 수열을 O(log(N)) 시간 안에 계산할 수 있다.
코드
#include <iostream>
#define MOD 1000000007
using namespace std;
typedef long long int ll;
struct M {
ll d[2][2];
};
M multiply(M a, M b);
M pow(M a, ll n);
ll N;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cin >> N;
M f = {{{1, 1}, {1, 0}}};
f = pow(f, N);
cout << f.d[1][0];
return 0;
}
M multiply(M a, M b) {
M c;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
int ans = 0;
for (int k = 0; k < 2; k++) ans += (a.d[i][k] * b.d[k][j]) % MOD;
c.d[i][j] = ans % MOD;
}
}
return c;
}
M pow(M a, ll n) {
if (n == 1) return a;
if (n % 2 == 1) return multiply(a, pow(a, n - 1));
M half = pow(a, n / 2);
return multiply(half, half);
}